Micron Document
🎖️GitЯра🎖️

Node / meshtastic / Meshtastic-Android / files / core / data / src / commonTest / kotlin / org / meshtastic / core / data / manager / NodeManagerImplTest.kt

Displaying Raw • Download

core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerImplTest.kt 2ea50d51466dc691890ee29c110fb089fb3fc567 (2ea50d51) Text, 20.67 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.data.manager

Tff7b72import T7ee787dev.mokkery.MockMode
Tff7b72import T7ee787dev.mokkery.answering.calls
Tff7b72import T7ee787dev.mokkery.answering.returns
Tff7b72import T7ee787dev.mokkery.every
Tff7b72import T7ee787dev.mokkery.everySuspend
Tff7b72import T7ee787dev.mokkery.matcher.any
Tff7b72import T7ee787dev.mokkery.mock
Tff7b72import T7ee787dev.mokkery.verify.VerifyMode.Companion.exactly
Tff7b72import T7ee787dev.mokkery.verifySuspend
Tff7b72import T7ee787kotlinx.coroutines.CompletableDeferred
Tff7b72import T7ee787kotlinx.coroutines.CoroutineStart
Tff7b72import T7ee787kotlinx.coroutines.async
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.test.TestScope
Tff7b72import T7ee787kotlinx.coroutines.test.runCurrent
Tff7b72import T7ee787kotlinx.coroutines.test.runTest
Tff7b72import T7ee787okio.ByteString
Tff7b72import T7ee787okio.ByteString.Companion.toByteString
Tff7b72import T7ee787org.meshtastic.core.model.MyNodeInfo
Tff7b72import T7ee787org.meshtastic.core.model.Node
Tff7b72import T7ee787org.meshtastic.core.model.NodeAddress
Tff7b72import T7ee787org.meshtastic.core.repository.NodeRepository
Tff7b72import T7ee787org.meshtastic.core.repository.NotificationManager
Tff7b72import T7ee787org.meshtastic.core.repository.RadioInterfaceService
Tff7b72import T7ee787org.meshtastic.core.repository.RadioSessionContext
Tff7b72import T7ee787org.meshtastic.proto.DeviceMetrics
Tff7b72import T7ee787org.meshtastic.proto.EnvironmentMetrics
Tff7b72import T7ee787org.meshtastic.proto.HardwareModel
Tff7b72import T7ee787org.meshtastic.proto.Telemetry
Tff7b72import T7ee787org.meshtastic.proto.User
Tff7b72import T7ee787kotlin.test.BeforeTest
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertFalse
Tff7b72import T7ee787kotlin.test.assertNotNull
Tff7b72import T7ee787kotlin.test.assertNull
Tff7b72import T7ee787kotlin.test.assertTrue
Tff7b72import T7ee787org.meshtastic.proto.NodeInfo Tff7b72as Te6edf3ProtoNodeInfo
Tff7b72import T7ee787org.meshtastic.proto.Position Tff7b72as Te6edf3ProtoPosition

Tff7b72class T56d364NodeManagerImplTest Tb4b4b4{

Tff7b72private Tff7b72val Te6edf3nodeRepositoryTb4b4b4: Te6edf3NodeRepository Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3notificationManagerTb4b4b4: Te6edf3NotificationManager Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3radioInterfaceServiceTb4b4b4: Te6edf3RadioInterfaceService Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3testScope Tff7b72= Te6edf3TestScopeTb4b4b4(Tb4b4b4)

Tff7b72private Tff7b72lateinit Tff7b72var Te6edf3nodeManagerTb4b4b4: Te6edf3NodeManagerImpl

Tf0883e@BeforeTest
Tff7b72fun Td2a8ffsetUpTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3nodeManager Tff7b72= Te6edf3NodeManagerImplTb4b4b4(Te6edf3nodeRepositoryTb4b4b4, Te6edf3notificationManagerTb4b4b4, Te6edf3radioInterfaceServiceTb4b4b4, Te6edf3testScopeTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`getOrCreateNode creates default user for unknown node`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getOrCreateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4)

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3numTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4.Te6edf3startsWithTb4b4b4(Ta5d6ff"Ta5d6ffMeshtasticTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3nodeNumTb4b4b4)Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3idTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`updateNodeAndPersist awaits the repository write`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3setNodeDbReadyTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setAllowNodeDbWritesTb4b4b4(Tff7b72trueTb4b4b4)
Tff7b72val Te6edf3writeStarted Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3releaseWrite Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Te6edf3writeStartedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3releaseWriteTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tff7b72val Te6edf3update Tff7b72= Te6edf3async Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff4T79c0ff2Tb4b4b4) Tb4b4b4} Tb4b4b4}
Te6edf3writeStartedTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3updateTb4b4b4.Te6edf3isCompletedTb4b4b4)
Te6edf3releaseWriteTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3updateTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)

Te6edf3verifySuspend Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff2Tb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72?.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`same-node persistence cannot finish with an older snapshot`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3setNodeDbReadyTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setAllowNodeDbWritesTb4b4b4(Tff7b72trueTb4b4b4)
Tff7b72val Te6edf3firstWriteStarted Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3secondWriteStarted Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3releaseFirstWrite Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3persisted Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3NodeTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72var Te6edf3invocation Tff7b72= T79c0ff0
Te6edf3everySuspend Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Tff7b72val Te6edf3node Tff7b72= Tffa657itTb4b4b4.Te6edf3argTff7b72<Te6edf3NodeTff7b72>Tb4b4b4(T79c0ff0Tb4b4b4)
Te6edf3invocationTff7b72+Tff7b72+
Tff7b72when Tb4b4b4(Te6edf3invocationTb4b4b4) Tb4b4b4{
T79c0ff1 Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3firstWriteStartedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3releaseFirstWriteTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Tb4b4b4}

T79c0ff2 Tff7b72-Tff7b72> Te6edf3secondWriteStartedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Tb4b4b4}
Te6edf3persisted Tff7b72+Tff7b72= Te6edf3node
Tb4b4b4}

Tff7b72val Te6edf3first Tff7b72= Te6edf3async Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff1Tb4b4b4) Tb4b4b4} Tb4b4b4}
Te6edf3firstWriteStartedTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3second Tff7b72=
Te6edf3asyncTb4b4b4(Te6edf3start Tff7b72= Te6edf3CoroutineStartTb4b4b4.Te6edf3UNDISPATCHEDTb4b4b4) Tb4b4b4{
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff2Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Te6edf3runCurrentTb4b4b4(Tb4b4b4)
Te6edf3assertFalseTb4b4b4(
Te6edf3secondWriteStartedTb4b4b4.Te6edf3isCompletedTb4b4b4,
Ta5d6ff"Ta5d6ffthe second upsert must not enter while the first owns its laneTa5d6ff"Tb4b4b4,
Tb4b4b4)

Te6edf3releaseFirstWriteTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3firstTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3secondTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(T79c0ff1Tb4b4b4, T79c0ff2Tb4b4b4)Tb4b4b4, Te6edf3persistedTb4b4b4.Te6edf3mapTb4b4b4(Te6edf3NodeTff7b72::Te6edf3lastHeardTb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72?.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`session-bound node persistence from a retired generation is rejected`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3oldSession Tff7b72= Te6edf3RadioSessionContextTb4b4b4(Te6edf3generation Tff7b72= T79c0ff7LTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ffble:sameTa5d6ff"Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setNodeDbReadyTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setAllowNodeDbWritesTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3runWithSessionLeaseTb4b4b4(Te6edf3oldSessionTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72false

Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeForSessionTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3oldSessionTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff4T79c0ff2Tb4b4b4) Tb4b4b4}
Te6edf3runCurrentTb4b4b4(Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff2Tb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72?.Te6edf3lastHeardTb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`node persistence is suppressed while database writes are disabled`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3setNodeDbReadyTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setAllowNodeDbWritesTb4b4b4(Tff7b72falseTb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff4T79c0ff2Tb4b4b4) Tb4b4b4}

Te6edf3verifySuspendTb4b4b4(Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff2Tb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72?.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedUser preserves existing user if incoming is default`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3existingUser Tff7b72=
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffMy Custom NameTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffMCNTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)

T8b949e// Setup existing node
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Te6edf3existingUserTb4b4b4) Tb4b4b4}

Tff7b72val Te6edf3incomingDefaultUser Tff7b72=
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffMeshtastic 5678Ta5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ff5678Ta5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3incomingDefaultUserTb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffMy Custom NameTa5d6ff"Tb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3hw_modelTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedUser updates user if incoming is higher detail`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
T8b949e// Use a non-UNSET hw_model so isUnknownUser=false (avoids new-node notification + getString)
Tff7b72val Te6edf3existingUser Tff7b72=
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffOld NameTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffONTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Te6edf3existingUserTb4b4b4) Tb4b4b4}

Tff7b72val Te6edf3incomingDetailedUser Tff7b72=
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffReal UserTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffRUTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V1Tb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3incomingDetailedUserTb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffReal UserTa5d6ff"Tb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V1Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3hw_modelTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedPosition updates node position`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3position Tff7b72= Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff4T79c0ff5T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff9T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(Te6edf3nodeNumTb4b4b4, T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4, Te6edf3positionTb4b4b4, T79c0ff0Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff5T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3latitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff9T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3longitude_iTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedPosition with zero coordinates preserves last known location but updates satellites`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3initialPosition Tff7b72= Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff4T79c0ff5T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff9T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3sats_in_view Tff7b72= T79c0ff1T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(Te6edf3nodeNumTb4b4b4, T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4, Te6edf3initialPositionTb4b4b4, T79c0ff1T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0LTb4b4b4)

T8b949e// Receive "zero" position with new satellite count
Tff7b72val Te6edf3zeroPosition Tff7b72= Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff0Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff0Tb4b4b4, Te6edf3sats_in_view Tff7b72= T79c0ff5Tb4b4b4, Te6edf3time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff1Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(Te6edf3nodeNumTb4b4b4, T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4, Te6edf3zeroPositionTb4b4b4, T79c0ff1T79c0ff0T79c0ff0T79c0ff1T79c0ff0T79c0ff0T79c0ff0LTb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff5T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3positionTb4b4b4.Te6edf3latitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff9T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3longitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff5Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3sats_in_viewTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff0T79c0ff0T79c0ff1Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedPosition for local node ignores purely empty packets`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNum Tff7b72= T79c0ff1T79c0ff1T79c0ff1T79c0ff1
Tff7b72val Te6edf3emptyPos Tff7b72= Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff0Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff0Tb4b4b4, Te6edf3sats_in_view Tff7b72= T79c0ff0Tb4b4b4, Te6edf3time Tff7b72= T79c0ff0Tb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(Te6edf3myNumTb4b4b4, Te6edf3myNumTb4b4b4, Te6edf3emptyPosTb4b4b4, T79c0ff0Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3myNumTff7b72]
T8b949e// Should still be null since the empty position for local node is ignored
Te6edf3assertNullTb4b4b4(Te6edf3resultTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedTelemetry updates lastHeard`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4) Tb4b4b4}

Tff7b72val Te6edf3telemetry Tff7b72= Te6edf3TelemetryTb4b4b4(Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3device_metrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= T79c0ff5T79c0ff0Tb4b4b4)Tb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedTelemetryTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3telemetryTb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertEqualsTb4b4b4(T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedTelemetry updates device metrics`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3telemetry Tff7b72= Te6edf3TelemetryTb4b4b4(Te6edf3device_metrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= T79c0ff7T79c0ff5Tb4b4b4, Te6edf3voltage Tff7b72= T79c0ff3.8fTb4b4b4)Tb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedTelemetryTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3telemetryTb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3deviceMetricsTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff7T79c0ff5Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3deviceMetricsTb4b4b4.Te6edf3battery_levelTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3.8fTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3deviceMetricsTb4b4b4.Te6edf3voltageTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedTelemetry updates environment metrics`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3telemetry Tff7b72=
Te6edf3TelemetryTb4b4b4(Te6edf3environment_metrics Tff7b72= Te6edf3EnvironmentMetricsTb4b4b4(Te6edf3temperature Tff7b72= T79c0ff2T79c0ff2.5fTb4b4b4, Te6edf3relative_humidity Tff7b72= T79c0ff4T79c0ff5.0fTb4b4b4)Tb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedTelemetryTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3telemetryTb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3environmentMetricsTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2T79c0ff2.5fTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3environmentMetricsTb4b4b4.Te6edf3temperatureTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff5.0fTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3environmentMetricsTb4b4b4.Te6edf3relative_humidityTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`clear resets internal state`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(T79c0ff1T79c0ff2T79c0ff3T79c0ff4Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffTestTa5d6ff"Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)

Te6edf3assertTrueTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Ta5d6ff"Ta5d6ff!000004d2Ta5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3myNodeNumTb4b4b4.Te6edf3valueTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`toNodeID returns broadcast ID for broadcast nodeNum`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3toNodeIDTb4b4b4(Te6edf3NodeAddressTb4b4b4.Te6edf3NODENUM_BROADCASTTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4, Te6edf3resultTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`toNodeID returns default hex ID for unknown node`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3toNodeIDTb4b4b4(T79c0ff0Te6edf3x1234Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(T79c0ff0Te6edf3x1234Tb4b4b4)Tb4b4b4, Te6edf3resultTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`toNodeID returns user ID for known node`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff5T79c0ff6T79c0ff7T79c0ff8
Tff7b72val Te6edf3userId Tff7b72= Ta5d6ff"Ta5d6ff!customidTa5d6ff"
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4)Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3toNodeIDTb4b4b4(Te6edf3nodeNumTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3userIdTb4b4b4, Te6edf3resultTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`removeByNodenum removes node from map`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= Te6edf3nodeNumTb4b4b4, Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!testnodeTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffTestTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffTTa5d6ff"Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Te6edf3assertTrueTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3containsKeyTb4b4b4(Te6edf3nodeNumTb4b4b4)Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Ta5d6ff"Ta5d6ff!testnodeTa5d6ff"Tb4b4b4)Tb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3removeByNodenumTb4b4b4(Te6edf3nodeNumTb4b4b4)

Te6edf3assertTrueTb4b4b4(Tff7b72!Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3containsKeyTb4b4b4(Te6edf3nodeNumTb4b4b4)Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Ta5d6ff"Ta5d6ff!testnodeTa5d6ff"Tb4b4b4)Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedUser sets publicKey from user public_key`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3pk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3existingUser Tff7b72=
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffExistingTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffEXTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Te6edf3existingUserTb4b4b4) Tb4b4b4}

Tff7b72val Te6edf3incomingUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffUpdatedTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffUPTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3pkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3incomingUserTb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72!!
Te6edf3assertEqualsTb4b4b4(Te6edf3pkTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3pkTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3public_keyTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3hasPKCTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedUser sets empty publicKey when key mismatch clears user key`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3existingPk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3existingUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffExistingTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffEXTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3existingPkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Te6edf3existingUserTb4b4b4, Te6edf3publicKey Tff7b72= Te6edf3existingPkTb4b4b4) Tb4b4b4}

Tff7b72val Te6edf3differentPk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1T79c0ff0Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3incomingUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffUpdatedTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffUPTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3differentPkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3incomingUserTb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72!!
T8b949e// Key mismatch: newUser gets public_key cleared to EMPTY, and publicKey should match
Te6edf3assertEqualsTb4b4b4(Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3public_keyTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`installNodeInfo sets publicKey from user public_key`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff5T79c0ff6T79c0ff7T79c0ff8
Tff7b72val Te6edf3pk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!abcd1234Ta5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffRemote NodeTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffRNTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3HELTEC_V3Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3pkTb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3info Tff7b72= Te6edf3ProtoNodeInfoTb4b4b4(Te6edf3num Tff7b72= Te6edf3nodeNumTb4b4b4, Te6edf3user Tff7b72= Te6edf3userTb4b4b4, Te6edf3last_heard Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3channel Tff7b72= T79c0ff0Tb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3installNodeInfoTb4b4b4(Te6edf3infoTb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72!!
Te6edf3assertEqualsTb4b4b4(Te6edf3pkTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3pkTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3public_keyTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3hasPKCTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`installNodeInfo clears publicKey for licensed users`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff5T79c0ff6T79c0ff7T79c0ff8
Tff7b72val Te6edf3pk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!abcd1234Ta5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffLicensed OpTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffLOTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3HELTEC_V3Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3pkTb4b4b4,
Te6edf3is_licensed Tff7b72= Tff7b72trueTb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3info Tff7b72= Te6edf3ProtoNodeInfoTb4b4b4(Te6edf3num Tff7b72= Te6edf3nodeNumTb4b4b4, Te6edf3user Tff7b72= Te6edf3userTb4b4b4, Te6edf3last_heard Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3channel Tff7b72= T79c0ff0Tb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3installNodeInfoTb4b4b4(Te6edf3infoTb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72!!
Te6edf3assertEqualsTb4b4b4(Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3public_keyTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`getMyNodeInfo returns null when repository has no info`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Tff7b72nullTb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getMyNodeInfoTb4b4b4(Tb4b4b4)

Te6edf3assertNullTb4b4b4(Te6edf3resultTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`getMyNodeInfo synthesizes from repository and nodeDB`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3repoInfo Tff7b72=
Te6edf3MyNodeInfoTb4b4b4(
Te6edf3myNodeNum Tff7b72= Te6edf3myNumTb4b4b4,
Te6edf3hasGPS Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3model Tff7b72= Ta5d6ff"Ta5d6fftbeamTa5d6ff"Tb4b4b4,
Te6edf3firmwareVersion Tff7b72= Ta5d6ff"Ta5d6ff2.5.0Ta5d6ff"Tb4b4b4,
Te6edf3couldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3shouldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3currentPacketId Tff7b72= T79c0ff1T79c0ff0T79c0ff0LTb4b4b4,
Te6edf3messageTimeoutMsec Tff7b72= T79c0ff5T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3minAppVersion Tff7b72= T79c0ff3T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3maxChannels Tff7b72= T79c0ff8Tb4b4b4,
Te6edf3hasWifi Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3channelUtilization Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3airUtilTx Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3deviceId Tff7b72= Tff7b72nullTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Te6edf3repoInfoTb4b4b4)

T8b949e// Add node with position (non-zero lat → hasGPS = true)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(Te6edf3myNumTb4b4b4, Te6edf3myNumTb4b4b4, Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff1T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3myNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!mydeviceTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TBEAMTb4b4b4)Tb4b4b4) Tb4b4b4}

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getMyNodeInfoTb4b4b4(Tb4b4b4)

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3myNumTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3myNodeNumTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3hasGPSTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fftbeamTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3modelTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff!mydeviceTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3deviceIdTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`getMyNodeInfo falls back to nodeDB model when repository model is null`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3repoInfo Tff7b72=
Te6edf3MyNodeInfoTb4b4b4(
Te6edf3myNodeNum Tff7b72= Te6edf3myNumTb4b4b4,
Te6edf3hasGPS Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3model Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3firmwareVersion Tff7b72= Ta5d6ff"Ta5d6ff2.5.0Ta5d6ff"Tb4b4b4,
Te6edf3couldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3shouldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3currentPacketId Tff7b72= T79c0ff1T79c0ff0T79c0ff0LTb4b4b4,
Te6edf3messageTimeoutMsec Tff7b72= T79c0ff5T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3minAppVersion Tff7b72= T79c0ff3T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3maxChannels Tff7b72= T79c0ff8Tb4b4b4,
Te6edf3hasWifi Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3channelUtilization Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3airUtilTx Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3deviceId Tff7b72= Tff7b72nullTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Te6edf3repoInfoTb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3myNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3HELTEC_V3Tb4b4b4)Tb4b4b4) Tb4b4b4}

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getMyNodeInfoTb4b4b4(Tb4b4b4)

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffHELTEC_V3Ta5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3modelTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedTelemetry with null metrics does not crash`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4) Tb4b4b4}

T8b949e// Telemetry with no metrics at all
Tff7b72val Te6edf3telemetry Tff7b72= Te6edf3TelemetryTb4b4b4(Te6edf3time Tff7b72= T79c0ff3T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)

Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedTelemetryTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3telemetryTb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`getMyId returns empty when disconnected`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Tff7b72nullTb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getMyIdTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`getMyId returns user ID when connected`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3setMyNodeNumTb4b4b4(Te6edf3myNumTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3myNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!mynode42Ta5d6ff"Tb4b4b4)Tb4b4b4) Tb4b4b4}

Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getMyIdTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff!mynode42Ta5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4)
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.17s